﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace _4Gewinnt
{
    class TournamentController
    {
        private _4gewinnt_GUI GUI;
        private Type[] contestants;
        private int nRounds;
        private bool showBoardInGUI;
        private int[] points;
        private int[] wins;
        private int[] draws;
        private int[] losses;
        private int nEngines;

        /// <summary>
        /// Points for a win
        /// </summary>
        private int pointsPerWin = 30;

        /// <summary>
        /// Minimum points for a win (after penalty)
        /// </summary>
        private int minPointsPerWin = 3;

        /// <summary>
        /// Points for a draw
        /// </summary>
        private int pointsPerDraw = 10;

        /// <summary>
        /// Minimum points for a draw (after penalty)
        /// </summary>
        private int minPointsPerDraw = 1;

        /// <summary>
        /// Points for a loss
        /// </summary>
        private int pointsPerLoss = 0;

        /// <summary>
        /// Minimum points for a loss (after penalty)
        /// </summary>
        private int minPointsPerLoss = 0;

        /// <summary>
        /// The number of free seconds per move (without getting a penalty)
        /// </summary>
        private double freeSeconds = 2;

        /// <summary>
        /// The penalty per second above freeSeconds
        /// </summary>
        private double penaltyPerSecond = 3;



        /// <summary>
        /// Initializes a new tournament
        /// </summary>
        /// <param name="contestants">The types of the contestants</param>
        /// <param name="nRounds">The number of games each engines plays against each opponent</param>
        /// <param name="GUI">The GUI to control by this object</param>
        /// <param name="showBoardInGUI">True if the engines moves shall be shown on the 
        /// game board in the GUI</param>
        public TournamentController(_4gewinnt_GUI GUI, Type[] contestants, int nRounds, bool showBoardInGUI)
        {
            this.GUI = GUI;
            this.contestants = contestants;
            this.nRounds = nRounds;
            this.showBoardInGUI = showBoardInGUI;
            this.nEngines = contestants.Length;
            this.points = new int[nEngines];
            this.wins = new int[nEngines];
            this.draws = new int[nEngines];
            this.losses = new int[nEngines];

        }


        /// <summary>
        /// Starts a new tournament. Results are shown in the game message window
        /// </summary>
        public void startTournament()
        {
            //start a round robin tournament
            for (int i = 0; i < nEngines; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    //let contestant[i] play against contestant[j] for nRounds rounds
                    for (int r = 0; r < nRounds; r++)
                    {
                        //Perform one match:

                        //Define contestants for this match
                        Engine p1 = Engine.Create(contestants[i], GameColor.Red);
                        Engine p2 = Engine.Create(contestants[j], GameColor.Blue);

                        //Start match
                        GameController G = new GameController(GUI);
                        G.ShowBoardInGUI = showBoardInGUI;
                        G.startGame(p1, p2);
                        countPoints(i, j, G);

                        //Re-match: p2 begins:
                        p1 = Engine.Create(contestants[i], GameColor.Red);
                        p2 = Engine.Create(contestants[j], GameColor.Blue);
                        G = new GameController(GUI);
                        G.ShowBoardInGUI = showBoardInGUI;
                        G.startGame(p2, p1);
                        countPoints(i, j, G);

                    }
                }
            }
            printResultsInGUI();



        }


        /// <summary>
        /// Counts the points for a game between red and blue player (includes penalties)
        /// </summary>
        /// <param name="red">Index of red player</param>
        /// <param name="blue">Index of blue player</param>
        /// <param name="G">The game they played</param>
        private void countPoints(int red, int blue, GameController G)
        {
            int penBlue = computePenalty(G.AvgTimeBlue);
            int penRed = computePenalty(G.AvgTimeRed);
            //count points and apply penalties
            if (G.game.Draw)
            {
                points[red] += Math.Max(minPointsPerDraw, pointsPerDraw - penRed);
                points[blue] += Math.Max(minPointsPerDraw, pointsPerDraw - penBlue);
                draws[red]++;
                draws[blue]++;
            }
            else
            {
                if (G.game.ActivePlayer == GameColor.Red)
                {
                    points[red] += Math.Max(minPointsPerWin, pointsPerWin - penRed);
                    points[blue] += Math.Max(minPointsPerLoss, pointsPerLoss - penBlue);
                    wins[red]++;
                    losses[blue]++;
                }
                else
                {
                    points[red] += Math.Max(minPointsPerLoss, pointsPerLoss - penRed);
                    points[blue] += Math.Max(minPointsPerWin, pointsPerWin - penBlue);
                    wins[blue]++;
                    losses[red]++;
                }
            }
        }


        /// <summary>
        /// Prints a summary of the results in the GUI
        /// </summary>
        private void printResultsInGUI()
        {
            //Show tournament result:
            GUI.gameMessage("===================");
            GUI.gameMessage("TOURNAMENT RESULTS:");
            GUI.gameMessage("Pos.\tPoints\tWins\tDraws\tLosses\tName");
            int nMatches = 2 * nRounds * (nEngines - 1);
            for (int i = 0; i < nEngines; i++)
            {
                //search maximum in points:
                int maxPos = 0;
                int max = points[maxPos];
                for (int j = 1; j < points.Length; j++)
                {
                    if (points[j] > max)
                    {
                        maxPos = j;
                        max = points[maxPos];
                    }
                }

                GUI.gameMessage(
                    (i + 1).ToString() + ".\t" +
                    points[maxPos].ToString() + "\t" +
                    wins[maxPos].ToString() + " (" + Math.Round((100.0 * wins[maxPos]) / nMatches) + "%)" + "\t" +
                    draws[maxPos].ToString() + "\t" +
                    losses[maxPos].ToString() + "\t" +
                    contestants[maxPos].ToString() + "\n");


                //clear maximum so that it is not found in the next iteration
                points[maxPos] = -1;

            }
        }

        /// <summary>
        /// Computes the penalty, an engine gets for needing time ms for a move
        /// </summary>
        /// <param name="time">The time in ms</param>
        /// <returns></returns>
        private int computePenalty(double time)
        {
            if (time < freeSeconds * 1000)
                return 0;
            time -= freeSeconds * 1000;
            return (int)Math.Round((time * penaltyPerSecond) / 1000);
        }







    }
}
